<template>
	<view class="vi_slider">
		<!-- 水平滑动条 -->
		<view class="vi_slider_content" :class="{'vi_slider_tipSpace':setting.tips=='float'&&tipSpace}" :style="contentStyle">
			<!-- 数值左部显示 -->
			<view class="vi_slider_aside" :style="{'width':asideWidth+'em'}" v-if="range && setting.tips==true">
				{{setDecimal(minValue)}}
			</view>
			<!-- 滑块主要区域 -->
			<view class="vi_slider_main" :style="mainStyle">
				<!-- 背景条 -->
				<view class="vi_slider_bar" :id="'bar'+id">
					<!-- 进度条 -->
					<view class="vi_slider_progress" :style="{
							'width': Math.abs(percentDiffer) +'%',
							'margin-left': percentDiffer >0? percentMin +'%' : percentMax +'%'
						}">
						<vi-style-controller
							compName="slideBar"
							control="background"
							:size="setting.size"
							:type="setting.type"
							:color="color"
							:height="barHeight"
							:radius="barH/2+'px'"
							:disabled="disabled"
							:readOnly="readOnly"
							active>
						</vi-style-controller>
					</view>
					
					<!-- 左滑块 -->
					<view class="vi_slider_block" :id="'btl'+id" :style="{'left': percentMin +'%'}"
						v-if="range"
						@touchstart="onTouchStart($event, 'left')"
						@touchmove.stop="onTouchMove($event, 'left')"
						@touchend="onTouchEnd($event, 'left')">
						<vi-style-controller
							compName="slide"
							:control="['border','text']"
							:size="setting.size"
							:type="setting.type"
							:color="color"
							:radius="setting.radius"
							:width="buttonWidth"
							:height="buttonHeight"
							:borderWidth="setting.buttonBorderWidth"
							:disabled="disabled"
							:readOnly="readOnly"
							:active="minAct||alwayAct"
							basicText
							basicBorder
							borderBox>
							<slot name="left"></slot>
						</vi-style-controller>
					</view>
						
					<!-- 右滑块 -->
					<view class="vi_slider_block" :id="'btr'+id" :style="{'left': percentMax +'%'}"
						@touchstart="onTouchStart($event, 'right')"
						@touchmove.stop="onTouchMove($event, 'right')"
						@touchend="onTouchEnd($event, 'right')">
						<vi-style-controller
							compName="slide"
							:control="['border','text']"
							:size="setting.size"
							:type="setting.type"
							:color="color"
							:radius="setting.radius"
							:width="buttonWidth"
							:height="buttonHeight"
							:borderWidth="setting.buttonBorderWidth"
							:disabled="disabled"
							:readOnly="readOnly"
							:active="maxAct||alwayAct"
							basicText
							basicBorder
							borderBox>
							<slot name="right"></slot>
						</vi-style-controller>
					</view>
					<!-- 数值跟随显示 -->
					<view class="vi_slider_barShow" v-if="setting.tips =='float'">
						<view class="vi_slider_tip" :class="minTipClass||''" v-if="range"
							:style="{
								'left': percentMin+'%',
								'top': tipsTop
							}">
							<text>{{setDecimal(minValue)}}</text>
							<text v-if="unit">{{unit}}</text>
						</view>
						<view class="vi_slider_tip" :class="maxTipClass||''"
							:style="{
								'left': percentMax+'%',
								'top': tipsTop
							}">
							<text>{{setDecimal(maxValue)}}</text>
							<text v-if="unit">{{unit}}</text>
						</view>
					</view>
				</view>
			</view>
			<!-- 数值右部显示 -->
			<view class="vi_slider_aside" :style="{'width':asideWidth+'em'}" v-if="setting.tips==true">
				{{setDecimal(maxValue)}}
			</view>
			<!-- 数值单位 -->
			<view v-if="unit && setting.tips==true">
				{{unit}}
			</view>
		</view>
		
	</view>
</template>

<script>
	export default {
		name:"viSlider",
		model:{
			prop:"value",
			event:"input"
		},
		props:{
			type:{
				type:[String,null],
				default:null
			},
			color:{
				type:[String,null],
				default:""
			},
			//预设尺寸,sm、md、lg
			size:{
				type:[String,null],
				default:null
			},
			//滑块圆角，可带单位，circle圆形，square方形
			radius:{
				type:[String,Number,null],
				default:null
			},
			//组件高
			height:{
				type:[String,null],
				default:""
			},
			//进度条背景高
			barHeight:{
				type:[String,null],
				default:""
			},
			//滑块按钮宽
			buttonWidth:{
				type:[String,null],
				default:""
			},
			//滑块按钮高
			buttonHeight:{
				type:[String,null],
				default:""
			},
			//按钮边框宽，单位px
			buttonBorderWidth:{
				type:[Number,String,null],
				default:null
			},
			//滑块一直激活
			alwayAct:{
				type:[Boolean,null],
				default:false
			},
			//开启选区，以两个按钮的内边为实际值的表示，内边之外为固定填充
			range:{
				type:[Boolean,null],
				default:false
			},
			min:{
				type:[Number,null],
				default:0
			},
			max:{
				type:[Number,null],
				default:null
			},
			//固定多少位小数
			decimals:{
				type:[Number,null],
				default:0
			},
			step:{
				type:[Number,null],
				default:0
			},
			value:{
				type:[String,Number,Array,null],
				default:0
			},
			//显示当前数值，false(不显示)、true(显示在进度条两端)、float(跟随滑块浮动)
			tips:{
				type:[Boolean,String,null],
				default:null
			},
			//显示数值为float时，是否允许占位，撑高行高
			tipSpace:{
				type:[Boolean,null],
				default: false
			},
			minTipClass:{
				type:[String,null],
				default:""
			},
			maxTipClass:{
				type:[String,null],
				default:""
			},
			disabled:{
				type:[Boolean,null],
				default:false
			},
			//只读，只能系统更改
			readOnly:{
				type:[Boolean,null],
				default:false
			},
			unit:{
				type:[String,null],
				default:""
			},
			refreshWatcher:{
				default:""
			}
		},
		data() {
			return {
				minValue:0,//小值，当前
				maxValue:0,//大值，当前
				oMinValue:0,//小值，记录。用于滑动过程中显示，滑动结束后同步
				oMaxValue:0,//大值，记录。用于滑动过程中显示，滑动结束后同步
				minAct:false,
				maxAct:false,
				clickPoint:{x:0},//临时变量，暂存触点状态
				id:"",//进度条id，用于区别其他slider，获取宽度
				barW:0,//进度条实际显示宽度，用于计算
				barH:0,//进度条实际显示高度，用于计算
				//方形圆角定义值
				squareRatio:0.2,
				btnW:null,
				btnH:null
			}
		},
		computed:{
			//将默认值内含到css、计算属性等地方，只内含真值
			//规避特殊使用的情况下，由prop传入undefined、null等无效数据导致的，默认值失效问题。例如组件嵌套且需要组件间参数传递
			//HBuilder X3.3.13; 微信开发者工具stable 1.05.2111300
			//微信小程序会对传入的undefined、null值根据参数类型输出不同空值(false、""、null、0)，参数类型为null或未指定参数类型则输出null
			//H5会对传入的undefined取默认值，未设默认值则输出undefined。对传入的null值固定输出null
			//做参数传递时需警惕两者行为不同而造成的陷阱，主要问题出现在微信小程序的空值转化，输出非用户传入的空值、假值，使默认真值丢失
			//兼容空值取默认值的可行方案目前为，参数类型加入null如[Boolean,null]，判断传入参数是否为定义的类型，是则使用，否则赋予默认值
			setting(){
				return {
					type: this.$api.isDefined(this.type)? this.type :"primary",
					size: this.$api.isDefined(this.size)? this.size :"md",
					radius: this.$api.isDefined(this.radius)? this.radius :"circle",
					buttonBorderWidth: this.$api.isDefined(this.buttonBorderWidth)? this.buttonBorderWidth :1,
					min: this.$api.isValue(this.min)? this.min :0,
					max: this.$api.isValue(this.max)? this.max :100,
					decimals: this.decimals ||0,
					step: this.step ||1,
					tips: this.$api.isDefined(this.tips)? this.tips :true,
				}
			},
			contentStyle(){
				return this.height? `min-height:${this.height};` :""
			},
			mainStyle(){
				//设置进度条最小高度（依据按钮高度定），左右两边预留距离显示文字（依据按钮宽度定）
				let res = {};
				if(this.btnH){
					res["min-height"] = this.btnH[0] + this.btnH[1];
				}
				if(this.btnW){
					res["margin-right"] = `calc(${this.btnW[0]/2}${this.btnW[1]} + 0.25em)`
					res["margin-left"] = `calc(${this.btnW[0]/2}${this.btnW[1]} + 0.25em)`
				}
				return this.$api.objToStr(res)
			},
			maxDecimals(){
				//最大值小数位、最小值小数位、步阶小数位、设置小数位，取最大值
				let stepLen = this.$api.lengthOf(this.setting.step, {type:"decimal"}),
					maxLen = this.$api.lengthOf(this.setting.max, {type:"decimal"}),
					minLen = this.$api.lengthOf(this.setting.min, {type:"decimal"});
				return Math.max(this.setting.decimals, stepLen, maxLen, minLen)
			},
			tipsTop(){
				if(this.setting.tips=='float' && this.btnH){
					return `calc(-${this.btnH[0]/2}${this.btnH[1]} - 0.5em)`
				}
				return "initial"
			},
			//小值所占百分比
			percentMin(){
				let min = this.setting.min;
				return (this.minValue - min)/(this.setting.max - min)*100
			},
			//大值所占百分比
			percentMax(){
				let min = this.setting.min;
				return (this.maxValue - min)/(this.setting.max - min)*100
			},
			//大小值区间所占百分比
			percentDiffer(){
				return (this.maxValue - this.minValue)/(this.setting.max - this.setting.min)*100
			},
			//左右数值宽度
			asideWidth(){
				let min = this.setting.min,
					max = this.setting.max,
					stepLen = this.$api.lengthOf(this.setting.step, {type:"decimal"}),
					maxLen = this.$api.lengthOf(max, {type:"decimal"}),
					minLen = this.$api.lengthOf(min, {type:"decimal"});
				let D = Math.max(this.setting.decimals, stepLen, maxLen, minLen),
					N = Math.max(String(parseInt(max)).length,String(parseInt(min)).length);
				return (N + D +(D>0? 1:0))/2
			}
		},
		methods: {
			setDecimal(value){
				let dLen = this.$api.lengthOf(value, {type:"decimal"}),
					decimals = this.setting.decimals;
				if(dLen>decimals){
					return value
				}
				return value.toFixed(decimals)
			},
			onTouchStart(e, type){
				if(e.touches.length>1){
					return
				}
				if(type=="left"){
					this.minAct = true
				}else if(type=="right"){
					this.maxAct = true
				}
				//禁用状态
				if(this.disabled||this.readOnly){
					return
				}
				this.clickPoint.x = e.touches[0].pageX;
			},
			onTouchMove(e, type){
				//禁用状态
				if(e.touches.length>1||this.disabled||this.readOnly){
					return
				}
				//如果是选区，收缩计算范围
				let newVal,
					min = this.setting.min,
					max = this.setting.max,
					val = (e.touches[0].pageX - this.clickPoint.x)/this.barW * (max - min);
				if(type=="left"){
					newVal = this.oMinValue + val;
					if(newVal<min){
						this.minValue = min
					}else if(newVal>max){
						this.minValue = max
					}else{
						this.minValue = this.$api.numberFix(newVal-(newVal-min)%this.setting.step, this.maxDecimals)
					}
				}else if(type=="right"){
					newVal = this.oMaxValue + val;
					if(newVal<min){
						this.maxValue = min
					}else if(newVal>max){
						this.maxValue = max
					}else{
						this.maxValue = this.$api.numberFix(newVal-(newVal-min)%this.setting.step, this.maxDecimals)
					}
				}
				//抛出事件
				this.emitEvent("changing");
			},
			onTouchEnd(e, type){
				let minValue = this.minValue,
					maxValue = this.maxValue;
				if(minValue > maxValue){
					this.minValue = maxValue;
					this.maxValue = minValue;
				}
				this.oMinValue = minValue;
				this.oMaxValue = maxValue;
				this.minAct = false;
				this.maxAct = false;
				//禁用状态
				if(this.disabled||this.readOnly){
					return
				}
				//抛出事件
				this.emitEvent("change");
			},
			refresh(){
				this.getWidth()
			},
			emitEvent(type){
				if(this.range){
					this.$emit("input", [this.minValue, this.maxValue]);
					this.$emit(type, [this.minValue, this.maxValue]);
				}else{
					this.$emit("input", this.maxValue);
					this.$emit(type, this.maxValue);
				}
			},
			//获取bar屏幕宽度，如果外部变更slider宽度，需调用刷新宽度
			getWidth(){
				const views = uni.createSelectorQuery().in(this);
				views.select(`#bar${this.id}`)
					.fields({
						size: true
					}, (data) => {
						if(data){
							this.barW = data.width
							this.barH = data.height
						}
					})
					.exec()
			},
			//获取按钮宽高，影响到组件高度，以及进度条位置
			getBtnSize(){
				if(!this.buttonWidth || !this.buttonHeight){
					const views = uni.createSelectorQuery().in(this);
					views.select(`#btr${this.id}`)
						.fields({
							size: true
						}, (data) => {
							if(data){
								this.btnW = [data.width, "px"];
								this.btnH = [data.height, "px"];
								//需重新获取进度条宽度
								this.$nextTick(()=>{
									this.getWidth();
								})
							}
						})
						.exec()
				}else{
					this.btnW = this.$api.splitUnit(this.buttonWidth);
					this.btnH = this.$api.splitUnit(this.buttonHeight);
					this.$nextTick(()=>{
						this.getWidth();
					})
				}
			},
		},
		watch:{
			"value":{
				immediate:true,
				handler(newVal){
					//对传入值进行校验
					let min = this.setting.min,
						max = this.setting.max;
					newVal = newVal||0;
					if(this.range){
						if(this.$api.isArray(newVal) && this.$api.isNumber(newVal[0],true) && this.$api.isNumber(newVal[1],true)){
							//验证，防止二次写入
							if(newVal[0]==this.minValue&&newVal[1]==this.maxValue){
								return
							}
							let a = Number(newVal[0]),
								b = Number(newVal[1]);
							if(a>b){
								[a,b] = [b,a]
							}
							if(a>max){
								this.minValue = this.oMinValue = max;
								this.maxValue = this.oMaxValue = max;
							}else if(b<min){
								this.minValue = this.oMinValue = min;
								this.maxValue = this.oMaxValue = min;
							}else{
								this.minValue = this.oMinValue = a;
								this.maxValue = this.oMaxValue = b;
								if(a<min){
									this.minValue = this.oMinValue = min;
								}
								if(b>max){
									this.maxValue = this.oMaxValue = max;
								}
							}
						}else{
							this.minValue = this.oMinValue = min;
							this.maxValue = this.oMaxValue = max;
							//无数据或不规范数据进行一次提交
							this.emitEvent("change");
						}
					}else{
						//验证，防止二次写入
						if(newVal==this.maxValue){
							return
						}
						if(newVal<min){
							this.maxValue = this.oMaxValue = min;
						}else if(newVal>max){
							this.maxValue = this.oMaxValue = max;
						}else{
							this.maxValue = this.oMaxValue = Number(newVal);
						}
					}
				}
			},
			"buttonWidth":{
				handler(newVal){
					this.getBtnSize();
				}
			},
			"buttonHeight":{
				handler(newVal){
					this.getBtnSize();
				}
			},
			"refreshWatcher":{
				handler(newVal){
					this.getBtnSize();
				}
			}
		},
		created() {
			this.id = `el${Math.ceil(Math.random() * 10e10).toString(36)}`;
			if(!this.range){
				this.minValue = this.setting.min
			}
			this.$nextTick(()=>{
				this.getBtnSize();
				uni.onWindowResize(()=>{
					this.getWidth();
				})
			})
		},
		destroyed() {
			uni.offWindowResize(()=>{})
		}
	}
</script>

<style lang="scss">
$bar-bg-color: #eee;
$block-background-color: #fff;
.vi_slider{
	.vi_slider_barShow{
		width: 100%;
		position: absolute;
		.vi_slider_tip{
			position: absolute;
			padding: 0 0.25em;
			background-color: rgba(0,0,0,.5);
			border-radius: 0.25em;
			color:#fff;
			transform: translate(-50%, -100%);
			&::after{
				content: "";
				width: 0;
				height: 0;
				position: absolute;
				top: 100%;
				left: 50%;
				border-top: 0.25em solid rgba(0,0,0,.75);
				border-right: 0.25em solid transparent;
				border-left: 0.25em solid transparent;
				font-size: 80%;
				transform: translateX(-50%);
			}
		}
	}
	.vi_slider_content{
		display: flex;
		align-items: center;
		&.vi_slider_tipSpace{
			padding-top: 1.8em;
		}
		.vi_slider_aside{
			display: flex;
			align-items: center;
			flex-shrink: 0;
			margin: 0 0.25em;
			&:last-child{
				justify-content: flex-end;
			}
		}
	}
	.vi_slider_main{
		flex-grow: 1;
		display: flex;
		align-items: center;
		.vi_slider_bar{
			width: 100%;
			position: relative;
			display: flex;
			align-items: center;
			background-color: $bar-bg-color;
			.vi_slider_progress{
				height: 100%;
			}
		}
		.vi_slider_block{
			position: absolute;
			top: 50%;
			left: 0;
			background-color: $block-background-color;
			transform: translate(-50%, -50%);
		}
	}
}
</style>
